package java8.stream;

import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Function;
import java.util.stream.*;

/**
 * @program: util
 * @description:
 * @author: zhouliangliang
 * @create: 2019-08-27 11:06
 **/
public class StreamTest {

   private List<Dish> menu = Arrays.asList(
            new Dish("pork", false, 800, Dish.Type.MEAT),
            new Dish("beef", false, 700, Dish.Type.MEAT),
            new Dish("chicken", false, 400, Dish.Type.MEAT),
            new Dish("french fries", true, 530, Dish.Type.OTHER),
            new Dish("rice", true, 350, Dish.Type.OTHER),
            new Dish("season fruit", true, 120, Dish.Type.OTHER),
            new Dish("pizza", true, 550, Dish.Type.OTHER),
            new Dish("prawns", false, 300, Dish.Type.FISH),
            new Dish("salmon", false, 450, Dish.Type.FISH) );

    /**
     * lambda 测试
     */
   public void lambdaTest() {
       //排序
       menu.sort(Comparator.comparingInt(Dish::getCalories));
       //逆序
       menu.sort(Comparator.comparingInt(Dish::getCalories).reversed());
       //比较器链
       menu.sort(Comparator.comparingInt(Dish::getCalories).thenComparing(Dish::getName));

   }

    /**
     * stream 按照name分组
     * @param
     */
    public void groupByName() {
       Map<String, List<Dish>> map =  menu.stream().collect(Collectors.groupingBy(Dish::getName));
       for(Map.Entry<String, List<Dish>> entry : map.entrySet()) {
           System.out.println(entry.getKey());
           System.out.println(entry.getValue().size());
       }
    }

    /**
     * stream 按照类别分组
     * @param
     */
    public void groupByType() {
      Map<Dish.Type, List<Dish>> map =  menu.stream().collect(Collectors.groupingBy(Dish::getType));
      for(Map.Entry<Dish.Type, List<Dish>> entry : map.entrySet()) {
          System.out.println(entry.getKey() + " " + entry.getValue().size());
      }
    }

    /**
     * 过滤list, 从list当中选择符合某一条件的食物
     * @param
     */
    public void filterMenu() {
      List<String> list =  menu.stream()
                .filter((a) -> a.getCalories() > 150)
                .sorted(Comparator.comparingInt(Dish::getCalories).reversed())
                .map(Dish::getName)
                .limit(2)
                .collect(Collectors.toList());
        System.out.println(list);
    }

    /**
     * map 操作接受一个函数，将一个输入转化成另一个输出
     * 去掉hello world中重复的字符
     * 需要得到一个字符流
     * flatmap
     * @param
     */
    public void flatMapTest() {
        List<String> list = Arrays.asList("hello", "world");

        /**
         * 此种方式，是把每一个word split之后转化成了一个流,形成了一个Stream<String[]> 不是Stream<String>
         */
        List result = list.stream().map(word -> word.split("")).distinct().collect(Collectors.toList());
        result.stream().forEach(System.out::println);

        /**
         * Arrays::stream 接受一个数组，产生一个Stream<String>
         * 但是问题是把hello和word分开了每一个word形成了一个流
         * 得到了一个流的列表
         */
        result = list.stream()
                .map(word -> word.split(""))
                .map(Arrays::stream)
                .distinct()
                .collect(Collectors.toList());
        result.stream().forEach(System.out::println);

        /**
         * flat map将多个流转化成一个流
         */
        result = list.stream()
                .map(word -> word.split(""))
                .flatMap(Arrays::stream)
                .distinct()
                .collect(Collectors.toList());
        result.stream().forEach(System.out::println);
    }

    /**
     * 给定两个列表，返回数对
     * 1 【1，2，3】
     * 2 【3，4】
     * 返回 (1,3)(1,4)(2,3)(2,4)(3,3)(3,4)
     * 目标 得到一个Stream<Integer>
     */
    public void flatMapTest2() {
       List<Integer> listOne = Arrays.asList(1,2,3);
       List<Integer> listTwo = Arrays.asList(3,4);

        /**
         * 错误的方式, 形成了Stream<Stream<int[]>
         */
       listOne.stream().map(i -> listTwo.stream().map(j -> new int[]{i,j})).collect(Collectors.toList());

       listOne.stream().flatMap(i -> listTwo.stream().map(j -> new int[]{i,j})).collect(Collectors.toList()).forEach( item ->{
           StringBuilder builder = new StringBuilder();
           builder.append("(");
           for(int i=0 ;i<item.length; i++) {
                builder.append(item[i])
                       .append(",");
           }
           System.out.println(builder.substring(0, builder.toString().length() -1) + ")");
       });
    }

    /**
     * anymatch
     * allmatch
     * nonematch
     */
    public void testMatch() {
        /**
         * 是不是有符合的
         */
        boolean test = menu.stream().anyMatch(item -> item.getCalories() > 500 );
        System.out.println(test);
        /**
         * 返回一个boolean,验证流里边是不是都符合给定的谓词
         */
        test = menu.stream().allMatch(item -> item.getCalories() < 500);
        System.out.println(test);

        /**
         * 是不是都不符合
         */
        test = menu.stream().noneMatch(item -> item.getCalories() > 500);
        System.out.println(test);

    }

    /**
     * findfirst
     * findany
     */
    public void testFind() {
        /**
         * 找到第一个
         */
        Optional<Dish> dish = menu.stream().filter(item -> item.getCalories() > 500).findFirst();
        System.out.println(dish.isPresent());

        Stream.of(1,2,3,4,5,6).map(a ->a *a).filter(a -> a % 3 == 0).findFirst().ifPresent(System.out::println);

        /**
         * 找到任意一个
         */
        menu.stream().filter(item ->item.getCalories() > 500).findAny().ifPresent(item->{
            System.out.println(item.getName());
        });

    }

    /**
     * reduce
     * reduce 规约操作，把stream当中的值规约为一个值
     * map 是转换操作，把一个值转换为另一个值
     * @param
     */
    public void testMapAndReduce() {
        /**
         * map 把一个值转化成另一个值
         */
        Stream.of(1,2,3,4,5).map( a -> a * a).forEach(System.out::println);
        System.out.println();

        int sum = Stream.of(1,2,3,4,5).reduce(0, (a, b) -> a + b);
        System.out.println(sum);
        System.out.println();

        Optional<Integer> num = Stream.of(1,2,3,4,5).map(x -> x * x).reduce((a,b) -> a + b);
        int a = num.isPresent() ? num.get() : -1;
        System.out.println(a);
    }

    /**
     * reduce计算最大最小值
     */
    public void testMinMax() {
        Optional<Integer> min = Stream.of(1,2,3,4,5).reduce((a, b) -> a < b ? a : b);
        System.out.println(min.get());

        Optional<Integer> min2 = Stream.of(1,2,3,4,5).reduce((a,b) -> Math.min(a,b));
        System.out.println(min2.get());

        Optional<Integer> min3 = Stream.of(1,2,3,4,5).reduce(Integer::min);
        System.out.println(min3.get());
    }

    /**
     * map -> mapToInt
     * @param
     */
    public void testMapSpecial() {
        menu.stream().mapToInt(Dish::getCalories).min().ifPresent(num -> System.out.println(num));

        menu.stream().mapToInt(Dish::getCalories).max().ifPresent(num -> System.out.println(num));
    }

    /**
     * IntStrema
     * LongStream
     * DoubleStream
     * iterate
     *
     */
    public void testRange() {
        OptionalInt value = IntStream.range(1, 100).max();
        System.out.println(value.getAsInt());


        long count = IntStream.rangeClosed(1, 100).filter(x -> x%2 == 0).count();
        System.out.println(count);

        LongStream.rangeClosed(1, 10).forEach(item -> System.out.println(item) );
        DoubleStream.iterate(1, a -> a+1).limit(10).forEach(item -> System.out.println(item));
    }

    /**
     * Stream.of
     * Arrays.Stream
     * iterate
     * generate 接受一个Supplier<T>类型的Lambda提供新的值
     * Files.lines
     */
    public void testGenStream() {
        Stream.of(1,2,3,4,5).forEach(item -> System.out.println(item));
        System.out.println();

        int []array = new int[]{1,2,3,4,5};
        System.out.println(Arrays.stream(array).count());
        System.out.println();

        LongStream.iterate(1, a -> a+1).limit(20).forEach(item -> System.out.println(item));

        Stream.generate(Math::random).limit(20).forEach(item -> System.out.println(item));

        try {
            /**
             * 1. data.txt 直接放到项目下
             * 2. 通过reduce操作读取文件当中的内容
             */
            String content = Files.lines(Paths.get("data.txt"), Charset.defaultCharset()).reduce("", (a, b) ->  a + b);
            System.out.println(content);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 统计
     * 最大值
     * 最小值
     * 平均值
     * 总和
     * 以上所有值
     * @param
     */
    public void testStatistics() {
        //最大值
        Optional<Dish> max = menu.stream().collect(Collectors.maxBy(Comparator.comparing(Dish::getCalories)));
        System.out.println(max.get().getCalories());

        //最小值
        Optional<Dish> min = menu.stream().collect(Collectors.minBy(Comparator.comparing(Dish::getCalories)));
        System.out.println(min.get().getCalories());

        //总和
        int total = menu.stream().collect(Collectors.summingInt(Dish::getCalories));
        System.out.println(total);

        //平均值
        double average = menu.stream().collect(Collectors.averagingInt(Dish::getCalories));
        System.out.println(average);

        //以上所有值
        IntSummaryStatistics statistics = menu.stream().collect(Collectors.summarizingInt(Dish::getCalories));
        System.out.println(statistics);
    }

    /**
     * 字符串连接
     */
    public void testJoining() {
        String value = menu.stream().map(item -> item.getName()).collect(Collectors.joining(", "));
        System.out.println(value);
    }

    /**
     * reducing
     * 广义规约
     */
    public void testReducing() {
        //最小值
        int min = menu.stream().collect(Collectors.reducing(Integer.MAX_VALUE, Dish::getCalories, (a,b)->a>b? b : a));
        System.out.println(min);

        //最大值
        int max = menu.stream().collect(Collectors.reducing(Integer.MIN_VALUE, Dish::getCalories, (a, b) -> a>b ? a : b ));
        System.out.println(max);

        //总和
        int total = menu.stream().collect(Collectors.reducing(0, Dish::getCalories, Integer::sum));
        System.out.println(total);
        total = menu.stream().collect(Collectors.summingInt(Dish::getCalories));
        System.out.println(total);
        total = menu.stream().map(item -> item.getCalories()).reduce(Integer::sum).get();
        System.out.println(total);
        total = menu.stream().mapToInt(Dish::getCalories).sum();
        System.out.println(total);
        //求和
        int value = Arrays.stream(new int[]{1,2,3,4,5}).reduce(0, (a,b) -> a+b);
        System.out.println(value);
    }

    /**
     * 分组，多级分组, 分区
     * @param
     */
    public void testGrouping() {
        //按照type分组
        Map<Dish.Type,List<Dish>> map = menu.stream().collect(Collectors.groupingBy(Dish::getType));
        for(Map.Entry<Dish.Type, List<Dish>> entry : map.entrySet()) {
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }
        System.out.println();

        //多级分组
        Map<Dish.Type, Map<CaloricLevel, List<Dish>>> maps =  menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.groupingBy(dish -> {
            if(dish.getCalories() < 300) return CaloricLevel.NORMAL;
            else if(dish.getCalories() >= 300 && dish.getCalories() <= 500) return CaloricLevel.DIET;
            else  return CaloricLevel.FAT;
        })));
        System.out.println();

        //按照自组收集数据
        Map<Dish.Type, Long> value = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.counting()));
        for(Map.Entry<Dish.Type, Long> entry : value.entrySet()) {
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }
        System.out.println();

        Map<Dish.Type, Optional<Dish>> value2 = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.maxBy(Comparator.comparing(Dish::getCalories))));

        //optional 转化成其他的类型
        Map<Dish.Type, Dish> value3 = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get) ));

        //对一组求和
        Map<Dish.Type, Integer> value4 = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.summingInt(Dish::getCalories)));

        //求一个分类下都有哪些热量类型
        Map<Dish.Type, Set<CaloricLevel>> value5 = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.mapping(dish ->{
            if(dish.getCalories() <300) return CaloricLevel.NORMAL;
            else if (dish.getCalories() >= 300 && dish.getCalories() < 500) return CaloricLevel.DIET;
            else return CaloricLevel.FAT;
        }, Collectors.toSet())));

        //分区
        Map<Boolean, List<Dish>> partion = menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian));
        for(Map.Entry<Boolean, List<Dish>> entry : partion.entrySet()) {
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }
        System.out.println();
    }

    /**
     * 将list中对象的两个属性转变成map
     * @param
     */
    public void testMap() {
       Map<String, Integer> map = menu.stream().collect(Collectors.toMap(Dish::getName, Dish::getCalories));
       for(Map.Entry<String, Integer> entry : map.entrySet()){
           System.out.println(entry.getKey() + "    " + entry.getValue());
       }
    }


    /**
     * 前n个自然数求和性能
     * @param
     */
    public long measureSumPref(Function<Long, Long> add, long n) {
        long fastest = Long.MAX_VALUE;
        for(int i=0; i<10; i++) {
            long start =System.nanoTime();
            long sum = add.apply(n);
            long duration = (System.nanoTime() - start) / 1000000; //毫秒
            System.out.println("result : " + sum);
            if(duration < fastest) fastest = duration;
        }
        return fastest;
    }



    public void sum() {
//        System.out.println(measureSumPref(ParallelStream::sequentialSum, 10000000));
//        System.out.println(measureSumPref(ParallelStream::parallelSum, 10000000));
        System.out.println(measureSumPref(ParallelStream::forkJoinSum, 10000000));
    }




    public static void main(String[] args) {
        new StreamTest().sum();
    }
}
